package playGround.square;


import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import playGround.border.Border;
import playGround.dungeon.Direction;

import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Raw;


import exceptions.IllegalBorderException;
import exceptions.IllegalTemperatureException;

/**
 * @invar 	The temperature of each square must be a valid temperature.
 * 			| isValidTemperature(this.getTemperature())
 * @invar   The temperatureScale must be a valid scale defined by enumtype.
 * 			| canHaveTemperatureScale(this.getTemperatureScale())
 * @invar   The minTemperature must be a valid minTemperature.
 * 			| canHaveMinTemperature(this.getMinTemperature())
 * @invar   The maxTemperature must be a valid maxTemperature.
 * 			| canHaveMaxTemperature(this.getMaxTemperature())
 * @invar	The humidity must be valid.
 * 			| canHaveHumidity(this.getHumidity())
 * @invar   The Map borders must be valid.
 * 			| areValidBorders()
 * @invar	The temperature and humidity of each square in the same area must be the same.
 * 			| for each square of this.getSquaresOfArea()
 * 			|	square.getTemperature()==this.getTemperature() && square.getHumidity()==this.getHumidity()
 * @invar   the squaresOfThisAreaNotSeparatedWithBorder must be a valid one.
 * 			| hasValidSquaresInArea()
 * @author 	Maarten Christiaen, Niels Claeys
 * @version 2.2
 */
public abstract class Square {
	/**
	 * The constructor of the Square class.
	 * @param 	temperature
	 * 			The temperature for this new square.
	 * @param 	humidity
	 * 			The humidity for this new square.
	 * @param	temperatureScale
	 * 			The temperaturescale for this new square.
	 * @param   borders
	 * 			the borders of the square
	 * @pre     The humidity must lie between 0 and 100 percent.
	 * 			|canHaveHumidity(humidity)
	 * @post 	The humidity of this new square is set to the given humidity.
	 * 			|new.getHumidity() == humidity
	 * @post	The temperature of this new square is set to the given temperature.
	 * 			|new.getTemperature() == temperature
	 * @post    The temperatureScale of the square is set to the indicated temperatureScale.
	 * 			|new.temperatureScale() == temperatureScale	
	 * @post    the state of the square is set to initialized.
	 * 			| new.getInitialized() == true
	 * @post	The minimum temperature is set to the default value.
	 * 			| Scale.changeScale(Scale.CELCIUS, this.getTemperatureScale(),new.getMinTemperature) == -200
	 * @post	The maximum temperature is set to the default value.
	 * 			| Scale.changeScale(Scale.CELCIUS, this.getTemperatureScale(),new.getMaxTemperature) == 5000
	 * @post	The directions are initialized
	 * 			|new.getDirections().contains(Direction.NORTH)
	 * 			|new.getDirections().contains(Direction.SOUTH)
	 * 			|new.getDirections().contains(Direction.WEST)
	 * 			|new.getDirections().contains(Direction.EAST)
	 * 			|new.getDirections().contains(Direction.CEILING)
	 * 			|new.getDirections().contains(Direction.FLOOR)
	 */
	@Raw
	public Square(int temperature, BigDecimal humidity, Scale temperatureScale) {
		setMinTemperature(Scale.changeScale(Scale.CELCIUS, temperatureScale, -200));
		setMaxTemperature(Scale.changeScale(Scale.CELCIUS, temperatureScale, 5000));
		setTemperatureScale(temperatureScale);
		this.squaresOfthisAreaNotSeparatedWithBorder = new HashSet<Square>();
		this.squaresOfthisAreaNotSeparatedWithBorder.add(this);
		this.borders = new HashMap<Direction, Border>(6);
		this.initializeDirections();
		setTemperature(temperature);
		setHumidity(humidity);
		this.isInitialized = true;

	}
	/**
	 * this method initializes the directions
	 * @post 	all directions are initialized.
	 * 			|new.getDirections().contains(Direction.NORTH)
	 * 			|new.getDirections().contains(Direction.SOUTH)
	 * 			|new.getDirections().contains(Direction.WEST)
	 * 			|new.getDirections().contains(Direction.EAST)
	 * 			|new.getDirections().contains(Direction.CEILING)
	 * 			|new.getDirections().contains(Direction.FLOOR)
	 */
	private void initializeDirections() {
		this.borders.put(Direction.NORTH, null);
		this.borders.put(Direction.EAST, null);
		this.borders.put(Direction.SOUTH, null);
		this.borders.put(Direction.WEST, null);
		this.borders.put(Direction.CEILING, null);
		this.borders.put(Direction.FLOOR, null);

	}
	/**
	 * Method that returns whether the temperature of this square is dependent upon the temperature
	 * of neighboring squares (who are not in the same area).
	 */
	public abstract boolean temperatureIsDependentUponNeighbours();
	/**
	 * The humidity of this square.
	 */
	private BigDecimal humidity;
	/**
	 * The temperatureScale of this square.
	 */
	private Scale temperatureScale;
	/**
	 * Variable registering whether or not this square is initialized.
	 */
	private boolean isInitialized;
	/**
	 * The minimum temperature possible in this square.
	 */
	private int minTemperature;
	/**
	 * The maximum temperature possible in this square.
	 */
	private int maxTemperature;
	/**
	 * The temperature of this square.
	 */
	private int temperature;
	/**
	 * The cold damage received while standing on this square.
	 */
	private int coldDamage;
	/**
	 * the heat damage received while standing on this square.
	 */
	private int heatDamage;
	/**
	 * The rust damage received while standing on this square.
	 */
	private int rustDamage;
	/**
	 * The rate of inhability of this square.
	 */
	private int inhability;
	/**
	 * The temperature at which you start taking heat damage.
	 */
	private static int heatLimit=35;
	/**
	 * The amount of degrees celcius it takes to increase the heat damage taken by one point.
	 */
	private static int heatRate=15;
	/**
	 * Variable registering the slipperiness of this square.
	 */
	private boolean slippery;
	/**
	 * Variable registering the sliperiness of the material this square is made of.
	 */
	private boolean slipperyMaterial;
	/**
	 * Map that keeps record of the borders of this square with the directions.
	 */
	private Map<Direction, Border> borders;

	/**
	 * Variable registering the state of this square (terminated or not).
	 */
	private boolean isTerminated;
	/**
	 * 
	 */
	private boolean resolveAreaBalance;
	/**
	 * Variable referencing a set of Squares. The set contains all Squares of the area of this Square if all doors would be closed.
	 */
	private Set<Square> squaresOfthisAreaNotSeparatedWithBorder;
	/**
	 * Variable registering whether a border can be removed from this square at this time.
	 */
	private boolean borderCanBeRemoved;
	/**
	 * Method returning whether this square is initialized.
	 * @return whether this square is initialized.
	 * 			| result == this.isInitialized
	 */
	protected boolean getInitialized(){
		return this.isInitialized;
	}
	/**
	 * Method to check if the given temperatureScale is a valid one.
	 * @param temperatureScale is the scale for the temperature at the moment.
	 * @return returns true if the temperatureScale is a valid one.
	 * 			| temperatureScale != null
	 */
	@Raw
	public boolean canHaveTemperatureScale(Scale temperatureScale){
		if(temperatureScale==null)
			return false;
		return true;
	}
	/**
	 * Method to get the temperatureScale of this square.
	 * @return the temperatureScale of this square.
	 */
	@Basic @Raw
	public Scale getTemperatureScale() {
		return this.temperatureScale;
	}
	/**
	 * sets the scale of this square to scale.
	 * @param scale the temperature scale to set
	 * @post The new temperatureScale of this square is the given scale. 
	 * 		 |new.getTemperatureScale() == scale
	 * @throws NullPointerException
	 * 			The given scale is null.
	 * 			|!canHaveTemperatureScale
	 */
	public void setTemperatureScale(Scale scale) throws NullPointerException {
		if(!canHaveTemperatureScale(scale))
			throw new NullPointerException();
		this.temperatureScale = scale;
		changeScale(scale);
	}
	
	/**
	 * Getter for the minimum temperature of this square.
	 * @return returns the minTemperature of this square
	 */
	@Basic @Raw
	public int getMinTemperature(){
		return this.minTemperature;
	}
	@Basic
	public boolean getResolveAreaBalance(){
		return this.resolveAreaBalance;
	}
	/**
	 * Method to check whether the given minTemperature is a valid one for this square.
	 * @param minTemperature is the minTemperature that is given to the method.
	 * @return returns if the minTemperature is a valid one. 
	 * 			If this square is initialized, the minTemperature must be higher than or equal to absolute zero(temperature) and lower than or equal to the maxTemperature and lower than or equal the actual temperature.
	 * 			If this square is not yet initialized, the minTemperature must be higher than or equal to absolute zero.
	 * 			|if(this.getInitialized())
	 * 			| 	result== ((minTemperature >= -273) && (minTemperature <= getTemperature())
	 * 			|else
	 * 			|	result == newTemperature>=-273 
	 */
	public boolean canHaveMinTemperature(int minTemperature){
		int newTemperature = Scale.changeScale(getTemperatureScale(), Scale.CELCIUS, minTemperature);
		if(getInitialized())
			return newTemperature >= -273 && minTemperature <= getTemperature();
		
		else{
			return newTemperature >= -273;
		}
	}
	/**
	 * Setter for minimum temperature of this square.
	 * @param temperature is the temperature given to the method
	 * @throws IllegalArgumentException: if the temperature is not a valid one.
	 * 			| ! canHaveMinTemperature(temperature)
	 * @post  The new minTemperature of this square is set to temperature.
	 * 			| new.minTemperature() = temperature
	 */
	public void setMinTemperature(int temperature)throws IllegalArgumentException{
		if(!canHaveMinTemperature(temperature))
			throw new IllegalArgumentException();
		else{
			this.minTemperature = temperature;
		}
	}
	
	/**
	 * Getter for maximum temperature of this square.
	 * @return the maxTemperature from this square
	 */
	@Basic @Raw
	public int getMaxTemperature(){
		return this.maxTemperature;
	}
	
	/**
	 * Checks if the given maxTemperature is a valid one for this square.
	 * @param maxTemperature is the maxTemperature that is given to the method.
	 * @return returns if the maxTemperature is a valid one.
	 * 			If this square is initialized, the maxTemperature must be lower than or equal to the highest integer value and higher than or equal to the minTemperature and higher than or equal the actual temperature.
	 * 			If this square is not yet initialized, the maxTemperature must be lower than or equal to the highest integer value and higher than or equal to the minimum temperature of this square.
	 * 			|if(this.getInitialized())
	 * 			| 	result== ((maxTemperature <= Integer.MAX_VALUE) && (maxTemperature >= getMinTemperature()))
	 * 			|else
	 * 			|	result == ((maxTemperature <= Integer.MAX_VALUE) && (maxTemperature >= getMinTemperature()))
	 */
	@Raw
	public boolean canHaveMaxTemperature(int maxTemperature){
		if(getInitialized())
			return maxTemperature <= Integer.MAX_VALUE && maxTemperature >= getTemperature();
		else{
			return maxTemperature <= Integer.MAX_VALUE && maxTemperature >= getMinTemperature();
		}
	}
	
	/**
	 * Setter for maximum temperature for this square.
	 * @param temperature is the temperature given to the method
	 * @throws IllegalArgumentException  if the temperature is not a valid one.
	 * 			| ! canHaveMaxTemperature(temperature)
	 * @post  The new maxTemperature of this square is set to temperature.
	 * 			| new.maxTemperature() = temperature
	 */
	public void setMaxTemperature(int temperature)throws IllegalArgumentException{
		if(canHaveMaxTemperature(temperature))
			this.maxTemperature = temperature;
		else{
			throw new IllegalArgumentException();
		}
	}
	
	/**
	 * Getter for temperature of this square.
	 * @return the temperature of this square
	 */
	@Basic @Raw
	public int getTemperature() {
		return this.temperature;
	}
	/**
	 * Setter for temperature of this square.
	 * @param temperature the temperature to set
	 * @post	The new temperature is set to the given temperature.
	 * 			|new.getTemperature() == temperature	
	 * @throws 	IllegalTemperatureException
	 * 			The given temperature is not a valid temperature for this square.
	 * 			|!isValidTemperature(temperature)
	 */
	public void setTemperature(int temperature)
			throws IllegalTemperatureException{
		if (!isValidTemperature(temperature))
			throw new IllegalTemperatureException(temperature);
		this.temperature=temperature;
		calculateHeatDamage();
		calculateColdDamage();
		if(getInitialized()){
			calculateRustDamage();
			calculateSlippery();
			calculateInhability();
		}
		if((getNbSquaresOfAreaNotSeparatedWithBorder()!=1)  && !(getResolveAreaBalance()))
			resolveAreaImbalance(this);	
		
	}
	
	/**
	 * Checks if the given temperature is a valid one for this square.
	 * @param 	temperature check if it is a correct temperature
	 * @return 	True if and only if the given temperature lies between the values: minTemperature and maxTemperature.
	 * 			| result==(temperature>=getMinTemperature() && temperature <=getMaxTemperature())
	 */
	@Raw
	public boolean isValidTemperature(int temperature) {
		return (temperature>=getMinTemperature() && temperature <=getMaxTemperature());
	}
	/**
	 * Method to change the scale of this square.
	 * @param 	newTemperatureScale you want to turn the scale into.
	 * @throws 	NullPointerException if the given newTemperatureScale is null
	 * 			| !canHaveTemperatureScale(newTemperatureScale)
	 * @post	the new temperature scale is the given newTemperatureScale.
	 * 			|new.getTemperatureScale() == newTemperatureScale
	 * @post	the new minTemperature is adjusted to the new scale.
	 * 			|new.getMinTemperature() == Scale.changeScale(getTemperatureScale(),newTemperatureScale,this.getMinTemperature())
	 * @post 	the new maxTemperature is adjusted to the new scale.
	 * 			|new.getMaxTemperature() == Scale.changeScale(getTemperatureScale(),newTemperatureScale,this.getMaxTemperature())
	 * @post 	the new temperature is adjusted to the new scale.
	 * 			|new.getTemperature() == Scale.changeScale(getTemperatureScale(),newTemperatureScale,this.getTemperature())
	 */
	public void changeScale(Scale newTemperatureScale) throws NullPointerException{
			if(! canHaveTemperatureScale(newTemperatureScale)){
				throw new NullPointerException();
			}
			if(newTemperatureScale != this.temperatureScale){
				setMinTemperature(Scale.changeScale(getTemperatureScale(),newTemperatureScale, getMinTemperature()));
				setMaxTemperature(Scale.changeScale(getTemperatureScale(),newTemperatureScale, getMaxTemperature()));
				this.temperature = Scale.changeScale(getTemperatureScale(),newTemperatureScale, getTemperature());
				this.temperatureScale = newTemperatureScale;
			}
	}
	/**
	 * This method calculates the coldDamage.
	 * @post If the temperature < -5 then the cold damage can be calculated.
	 * 			| if(this.getTemperature() < -5)
	 * 			| 	new.getColdDamage() == (this.getTemperature + 5)/10
	 * @post If the temperature is not smaller than -5 then the coldDamage is set to 0
	 * 			|else
	 * 			|	new.getColdDamage() == 0
	 */
	private void calculateColdDamage(){
		int temperature = Scale.changeScale(getTemperatureScale(),Scale.CELCIUS,getTemperature());
		if(temperature <-5){
			this.coldDamage = (temperature + 5)/10;
		}
		else
			this.coldDamage = 0;
	}
	/**
	 * returns the heatLimit of squares
	 * @return the heatLimit of squares
	 */
	public static int getHeatLimit() {
		return heatLimit;
	}
	/**
	 * returns the heatRate of squares
	 * @return the heatRate of squares
	 */
	public static int getHeatRate() {
		return heatRate;
	}
	/**
	 * Setter for the heatLimit of squares.
	 * @param newHeatLimit the new heatLimit to set.
	 * @post the heatLimit is set to newHeatLimit
	 * 		| new.getHeatLimit() = newHeatLimit
	 */
	public static void setHeatLimit(int newHeatLimit) {
		heatLimit = newHeatLimit;
	}
	/**
	 * Setter for the heatRate of squares.
	 * @param newHeatRate the new heatrate to set.
	 * @post the heatrate is set to newHeatRate
	 * 		 | new.getHeatRate() = newHeatRate
	 */
	public static void setHeatRate(int newHeatRate) {
		heatRate = newHeatRate;
	}
	/**
	 * This method calculates the heatDamage.
	 * @post if the temperature greater than 35 we calculate the heatDamage
	 * 			| if(getTemperature()>35)
	 * 			| new.getHeatDamage() == (getTemperature() - getHeatLimit())/getHeatRate()
	 * @post else the heatDamage is set to 0
	 * 			| new.getHeatDamage() == 0
	 */
	private void calculateHeatDamage(){
		int temperature = Scale.changeScale(getTemperatureScale(),Scale.CELCIUS,getTemperature());
		if(temperature>35)
			 this.heatDamage = (getTemperature() - getHeatLimit())/getHeatRate();
		else
			this.heatDamage = 0;
		
	}
	/**
	 * this method gives the coldDamage of this square.
	 * @return 	the coldDamage of this square.
	 * 			| if(this.getTemperature() < -5)
	 * 			| 	result == (this.getTemperature() + 5)/10
	 * 			| else
	 * 			| 	result == 0
	 */
	public int getColdDamage(){
		return this.coldDamage;
	}
	/**
	 * This method returns the heatDamage
	 * @return 	the heatDamage of this square.
	 * 			| if(getTemperature()>35)
	 * 			| 	result == (getTemperature() - getHeatLimit())/getHeatRate()
	 * 			| else
	 * 			| 	result == 0
	 */
	public int getHeatDamage(){
		return this.heatDamage;
	}
	/**
	 * This method returns the humidity.
	 * @return the humidity of this square
	 */
	@Basic @Raw
	public BigDecimal getHumidity(){
		BigDecimal humi = new BigDecimal(this.humidity.doubleValue());
		return humi.setScale(2, RoundingMode.HALF_UP);
	}
	
	/**
	 * Checks whether the given humidity is a valid one for this square.
	 * @param humidity 
	 * 			the humidity to check
	 * @return whether or not the humidity is valid.
	 * 			| result == (humidity>=0 && humidity<=100)
	 */
	@Raw
	public boolean canHaveHumidity(BigDecimal humidity){
		return this.humidity.floatValue()>=0 && this.humidity.floatValue()<=100;
	}
	/**
	 * Method to set the given humidity
	 * @pre The humidity must be situated in the valid range
	 * 		| canHaveHumidity(humidity)
	 * @param humidity to set for this square.
	 * @post the humidity is set to the given value.
	 * 		| new.getHumidity() == humidity
	 */
	public void setHumidity(BigDecimal humidity){
		this.humidity = humidity.setScale(2, RoundingMode.HALF_UP);
		calculateRustDamage();
		calculateInhability();
		calculateSlippery();
		if((getNbSquaresOfAreaNotSeparatedWithBorder()!=1)  && !(getResolveAreaBalance()))
			resolveAreaImbalance(this);
	}
	/**
	 * This method calculates the rustDamage due to humidity
	 * @post if the humidity is larger than 30 percent, then the rust damage will be calculated
	 * 			|if(this.getHumidity()>30f)
	 * 			| new.getRustDamage() == (int) ((this.getHumidity()-30)/7)
	 * @post else the humidity is set to 0.
	 * 			new.getRustDamage() == 0.
	 */
	private void calculateRustDamage(){
		if(getHumidity().floatValue() > 30f){
			this.rustDamage = ((getHumidity().subtract(new BigDecimal(30))).divide(new BigDecimal(7),2)).intValue();
		}
		else{
			this.rustDamage = 0;
		}
	}
	/**
	 * This method returns the rustDamage
	 * @return the rustDamage of this square
	 * 			|if(this.getHumidity()>30f)
	 * 			| 	result == (int) ((this.getHumidity()-30)/7)
	 * 			|else
	 * 			|	result == 0
	 */
	public int getRustDamage(){
		return this.rustDamage;
	}
	/**
	 * This method returns the inhability.
	 * @return 	the inhability of this square.
	 * 			|result==(-(Math.sqrt(Math.pow(getHeatDamage(),3)))/(101-getHumidity())-Math.sqrt(getColdDamage()))
	 */
	public int getInhability(){
		return this.inhability;
	}
	/**
	 * Method to calculate the inhability of this square.
	 * @post the inhability of the square is calculated.
	 * 		| new.getInhability() == (-(Math.sqrt(Math.pow(getHeatDamage(),3)))/(101-getHumidity())-Math.sqrt(getColdDamage()))
	 * @post when there is overflow the inhability will be set to the maximum value
	 * 			|new.getInhability() == Integer.Max_VALUE
	 */
	private void calculateInhability(){
		if(getHeatDamage()> Math.pow(Integer.MAX_VALUE, (new BigDecimal(1).divide(new BigDecimal(3),16,RoundingMode.HALF_UP)).doubleValue()))
			this.inhability = Integer.MAX_VALUE;
		else
			this.inhability =  (int) (-(Math.sqrt(Math.pow(getHeatDamage(),3)/(101-getHumidity().intValue())))-Math.sqrt(getColdDamage()));
		
	}
	/**
	 * This method returns whether it is slippery in this square.
	 * @return whether or not this square is slippery
	 */
	@Basic @Raw
	public boolean getSlippery(){
		return this.slippery;
	}
	/**
	 * This method returns whether the square is made of slippery material.
	 * @return 	whether or not this square is made of slippery material
	 * 		|if (for some border in this.getAllBorders(): border.getSlippery()==true)
	 * 		|	then result==true
	 * 		|else result==false
	 */
	@Basic @Raw
	public boolean getSlipperyMaterial(){
		return this.slipperyMaterial;
	}
	
	/**
	 * This method sets slippery Material to true or false depending on whether the material is slippery.
	 * @param isSlipperyMaterial true if the material is slippery
	 * @post the field slipperyMaterial is set to true if one of the borders of this Square is slippery.
	 * 		|if (for some border in this.getAllBorders(): border.getSlippery()==true)
	 * 		|	then new.getSlipperyMaterial()==true
	 */
	public void setSlipperyMaterial(){
		this.slipperyMaterial=false;
		for(Border border1: borders.values()){
			if(border1 != null){
				if(border1.getSlippery())
					this.slipperyMaterial = true;
			}	
		}
		calculateSlippery();
	}
	/**
	 * Method to calculate the slipperiness of this square.
	 * @post 	the slipperiness of this square is calculated
	 * 			|if(this.getHumidity() == 100f && this.getTemperature()> 0)
	 * 			|	new.getSlippery()==true
	 * 			|else if(this.getTemperature() <=0 && this.getHumidity()>10f)
	 * 			|	new.getSlippery() == true
	 * 			|else if (getSlipperyMaterial())
	 * 			|	new.getSlippery() == true
	 * 			|else
	 * 			|	new.getSlippery()==false
	 */
	private void calculateSlippery(){
		this.setSlippery(getSlipperyMaterial());
		if(this.getHumidity().floatValue() ==new BigDecimal(100.00).floatValue() && this.getTemperature()> 0)
			this.setSlippery(true);
		else if(this.getTemperature() <=0 && this.getHumidity().floatValue()>10f){
			this.setSlippery(true);
		}
			
	}
	/**
	 * This method sets the sliperiness of this square to the given isSlippery.
	 * @param isSlippery whether the square is slippery or not
	 * @post sets slippery to isSlippery
	 * 		|new.getSlippery() = isSlippery
	 */
	private void setSlippery(boolean isSlippery){
		this.slippery = isSlippery;
	}
	/**
	 * Method to add a border to this Square.
	 * @param borders
	 * 			the map to add more than one border each to a specific direction
	 * @post for each direction of the given map borders, the border in that direction of this square is set to the border value of the given map.
	 * 		| for each direction in borders.keySet()
	 * 		| new.getBorder(direction) == borders.get(direction)
	 * @post for each value of the given map borders, a reference to this square is added. 
	 * 		| for each direction in borders.keySet()
	 * 		| new.getAllBorders().get(direction).containsSquare(this)
	 * @throws  IllegalBorderException
	 * 			When the given borders are not valid.
	 * 			|!canHaveBorders(borders)
	 */
	public void addBorder(Map<Direction, Border> borders)throws IllegalBorderException{
		if(!canHaveBorders(borders))
			throw new IllegalBorderException();
		for(Direction key:borders.keySet()){
//			if(!canHaveAsBorder(key, borders.get(key)))
//				throw new IllegalBorderException();
			this.borders.put(key, borders.get(key));
			borders.get(key).addSquare(this);
		}
		this.setSlipperyMaterial();
		this.setBorderCanBeRemoved();
	}
	
	/**
	 * this method adds a border to a specific direction
	 * @param direction to which the border is added
	 * @param border you want to add to a specific direction
	 * @post the border is added to the direction and a reference of this square is added to the border
	 * 		| new.getBorder(direction) == border
	 * @post 
	 * 		| border.getSquareAt(getNbSquares() +1) == this
	 * @throws  IllegalBorderException
	 * 			When the given combination direction, border is not valid.
	 * 			|!canHaveAsBorder(direction, border)
	 */
	public void addBorder(Direction direction, Border border)throws IllegalBorderException{
		if(!canHaveAsBorder(direction, border))
			throw new IllegalBorderException();
		this.borders.put(direction, border);
		border.addSquare(this);
		this.setSlipperyMaterial();
		this.setBorderCanBeRemoved();
	}
	/**
	 * this method says whether or not this border can exist in that direction
	 * @param direction in which you want to add the border
	 * @param border that you want to add
	 * @return false if direction or border is null or the place is already taken or the border cannot be on the floor
	 * 		| if(direction == null || border == null ||(this.getBorder(direction)!= null))
	 * 		| 	then result == false
	 * 		| else if(direction == Direction.FLOOR && !border.canBeOnFloor())
	 * 		| 	then result == false
	 * 		| else result == true
	 */
	public boolean canHaveAsBorder(Direction direction, Border border){
		if(direction == null || border == null)
			return false;
		if(direction == Direction.FLOOR){
			if(!border.canBeOnFloor())
				return false;
		}
		if(this.getBorder(direction)!= null)
			return false;
		return true;
	}
	/**
	 * Method to remove a border from this Square.
	 * @param direction
	 * 			the direction of the border to remove
	 * @post	the border is removed in the specific direction and the reference to this square is also removed.
	 * 		| 	new.getBorder(direction) == null 
	 * 		| ! getBorder(direction).containsSquare(this)
	 * @throws IllegalBorderException
	 * 			When border cannot be removed.
	 * 			|!this.getBorderCanBeRemoved()
	 * @throws IllegalArgumentException
	 * 			When this square doesn't have a border in the given direction.
	 * 			|this.getAllBorders().get(direction)==null
	 */
	public void removeBorder(Direction direction) throws IllegalBorderException, IllegalArgumentException{
		if(!this.borderCanBeRemoved)
			throw new IllegalBorderException();
		if(this.borders.get(direction)==null)
			throw new IllegalArgumentException();
		this.borders.get(direction).terminate();
		this.borders.remove(direction);
		this.setSlipperyMaterial();
		this.setBorderCanBeRemoved();
	}
	/**
	 * this method removes a border from this square.
	 * @param border border to be removed
	 * @post the border is removed from the map of borders
	 * 		| ! (getAllBorders.values().contains(border)) 
	 * @throws IllegalBorderException 
	 * 		when the border is null or if the border doesn't yet contains this square or if the border cannot be removed
	 * 		| if(border == null)  || (border.containsSquare(this))||(!getBorderCanBeRemoved())
	 * @note
	 * 		deze methode mag enkel worden opgeroepen via border.
	 */
	public void removeBorder(Border border) throws IllegalBorderException{
		if(border == null  || (border.containsSquare(this))||(!this.borderCanBeRemoved))
			throw new IllegalBorderException();
		for(Iterator <Border> it = this.borders.values().iterator();it.hasNext();){
			Border selectedBorder = it.next();
			if(selectedBorder==border){
				it.remove();
				selectedBorder.terminate();
				}
		}
		this.setSlipperyMaterial();
		this.setBorderCanBeRemoved();
	}
	protected void setBorderCanBeRemoved(boolean canBeRemoved) {
		this.borderCanBeRemoved=canBeRemoved;
	}
	protected void setBorderCanBeRemoved(){
		this.borderCanBeRemoved=(this.getNbBorders()>1);
	}
	protected boolean getBorderCanBeRemoved(){
		return this.borderCanBeRemoved;
	}
	public int getNbBorders(){
		int nbBorders=0;
		for(Border border:getAllBorders().values()){
			if(border!=null)
				nbBorders++;
		}
		return nbBorders;
	}
	
	/**
	 * Getter for the Map of borders.
	 * @return 	the map of borders.
	 */
	@Basic
	public Map<Direction,Border> getAllBorders(){
		return new HashMap<Direction,Border>(this.borders);
	}
	/**
	 * Method to check if this Square has a border at given direction.
	 * @param direction	
	 * 			the direction of the border to access
	 * @return	the border in the specified direction
	 * 			| result == this.getAllBorders().get(direction)
	 */
	public Border getBorder(Direction direction) {
		return this.borders.get(direction);
	}
	/**
	 * the method returns all the directions
	 * @return all the directions of the square
	 * 			| result == this.getAllBorders.keySet()
	 */
	public Set<Direction> getDirections(){
		return borders.keySet();
	}
	/**
	 * this method checks whether or not the square has this border
	 * @param border that is or is not part of the square
	 * @return true if the square has this border
	 * 			| this.getAllBorders.containsValue(border)
	 */
	public boolean hasBorder(Border border){
		return this.borders.containsValue(border);
	}
	/**
	 * the method checks whether or not the map of borders is valid
	 * @param borders from which you want to check the validity
	 * @return whether the borders are valid
	 * 		| if(this.getAllBorders()==null)
	 * 		| then result == false
	 * 		| for each border in this.getAllBorders().values() 
	 * 		|	if(border != null)
	 * 		| 	count++
	 * 		| if(count<1)
	 * 		| then result== false
	 */
	public boolean canHaveBorders(Map<Direction, Border> borders) {
		if(borders==null)
			return false;
		boolean hasAtLeastOneBorder = false;
		for(Border border: borders.values() ){
			if(border!=null)
				hasAtLeastOneBorder = true;
		}
		return hasAtLeastOneBorder;
	}
	/**
	 * The method checks whether the map borders is valid.
	 * @return  true if the map borders is valid and it's size is 6.
	 * 			|if(! this.canHaveBorders(getAllBorders()))
	 * 			| then result == false
	 * 			|else if(getAllBorders().size() != 6)
	 * 			| then result == false
	 * @return	true if each border of this square has a reference to this square and is not terminated.
	 * 			|for each border in getAllBorders().values()
	 * 			| 	if(!border.containsSquare(this) || border.isTerminated())
	 * 			| 	then result == false
	 * @return 	true if there aren't more than 3 doors bordering this square.
	 * 			|for each border in getAllBorders().values()
	 * 			|	if(border instanceof Door)
	 * 			|		amountDoors++
	 * 			|if(amountDoors>3)
	 * 			| then result == false
	 * @return	true if there is no door on the floor.
	 * 			|else if( ! this.getBorder(Direction.FLOOR).canBeOnFloor())
	 * 			| then result == false
	 * @return   it must have at least one boder
	 * 			|if(!canHaveBorders(getAllBorders)
	 * 			| then result == false
	 */
	public boolean areValidBorders(){
		
		if(this.borders.size()!=6)
			return false;
		else if(!this.canHaveBorders(this.borders))
			return false;
		for(Border border: this.borders.values()){
			if(!border.containsSquare(this) || border.isTerminated())
				return false;
		}
		if( ! this.getBorder(Direction.FLOOR).canBeOnFloor())
			return false;
		return true;
	}

	/**
	 * This method controls the different options when we two squares come together.  It makes a decision on which border to add, or not.
	 * @param square the square with which you share a border
	 * @param direction the direction in which you share a border
	 * @post if both directions don't have a border yet then the squares are in the same area
	 * 		| if(this.getBorder(direction) == null && square.getBorder(complementDirection)==null)
	 * 		| then for each Square squareOfArea of this.getSquaresOfthisAreaIfAllDoorsWouldBeClosed()
	 * 		|	(new squareOfAea).getSquaresOfthisAreaIfAllDoorsWouldBeClosed().contains(square)
	 * @post if one of the directions doesn't have a border, the other border is added here
	 * 		| else if(square.getBorder(complementDirection)==null)
	 * 		| then square.addBorder(complementDirection, this.getBorder(direction))
	 * 		| else if(this.getBorder(direction) == null)
	 * 		| then this.addBorder(direction, square.getBorder(complementDirection))
	 * 
	 * @post else they both have a border and then you have to resolve the border conflict
	 * 		|else
	 * 		| then resolveBorderConflict(square, direction, complementDirection)
	 * @throws 	NullPointerException
	 * 			The given direction or square is a null value.
	 * 			|square == null || direction == null
	 */
	public void controlBorder(Square square, Direction direction) throws NullPointerException{
		if(square == null || direction == null){
			throw new NullPointerException();
		}
		Direction complementDirection=Direction.getComplement(direction);
		Border borderOfThisSquare= this.getBorder(direction);
		Border borderOfOtherSquare=square.getBorder(complementDirection);
		if(borderOfThisSquare == null && borderOfOtherSquare==null){
			for(Square squareOfArea: this.getSquaresOfthisAreaNotSeparatedWithBorder())
				squareOfArea.addSquareToArea(square);
		}
		else if(borderOfOtherSquare==null){
			square.addBorder(complementDirection, borderOfThisSquare);
		}
		else if(borderOfThisSquare == null){
			this.addBorder(direction, borderOfOtherSquare);
		}
		else{
			resolveBorderConflict(square, direction, complementDirection);	
		}
	}
	/**
	 * This method resolves a border conflict between two squares. This means that the most closed form of borders stays instead of the other
	 * @param 
	 * 		|square the other square with a conflicting border
	 * @param 
	 * 		|direction in which the the other square is located
	 * @param 
	 * 		|complementDirection in which this square is located for the other square
	 * 
	 * @post if both borders can be opened or if both cannot, then we keep the one that already existed
	 * 		| if(square.getBorder(complementDirection).canBeOpenedOrClosed()==this.getBorder(direction).canBeOpenedOrClosed())
	 * 		| then  square.removeBorder(complementDirection);
			| 		square.addBorder(complementDirection, this.getBorder(direction))
	 *
	 *@post if one can be opened then we will replace that one with the one that cannot be opened
	 *		|else if(square.getBorder(complementDirection).canBeOpenedOrClosed())
	 *		| then  square.removeBorder(complementDirection);
			| 		square.addBorder(complementDirection, this.getBorder(direction))
			| else
			|then   this.removeBorder(direction);
			|		this.addBorder(direction, square.getBorder(complementDirection))
	 */
	private void resolveBorderConflict(Square square, Direction direction,Direction complementDirection){
		this.borderCanBeRemoved = true;
		square.setBorderCanBeRemoved(true);
		if(square.getBorder(complementDirection).canBeOpenedOrClosed()==this.getBorder(direction).canBeOpenedOrClosed()) {
			square.removeBorder(complementDirection);
			square.addBorder(complementDirection, this.getBorder(direction));
		}
		else if(square.getBorder(complementDirection).canBeOpenedOrClosed()){
			square.removeBorder(complementDirection);
			square.addBorder(complementDirection, this.getBorder(direction));
		}
		else{
			this.removeBorder(direction);
			this.addBorder(direction, square.getBorder(complementDirection));
		}
		this.setBorderCanBeRemoved();
		square.setBorderCanBeRemoved();
	}
	
	/**
	 * This method says whether or not the square is in the same area as this square
	 * @param square the square from which you want to know if it lies in the same area
	 * @return false if square is null or if it is not in this area
	 * 		| if(square == null || !getSquaresOfThisArea().contains(square)
	 * 		| return false
	 * @note this method searches every square in the area for an open door.  If there is one than the whole area of this square is added to the
	 * Set.  Now if we assume that to add a square to the set and to use the contains operation on the set, happens in constant time then
	 *   
	 * the time complexity of this method is: 6*N 
	 * 
	 * where N is the number of squares in the whole set. And the 6 comes from the fact that every square has 6 borders that you must check.
	 */
	public boolean isInSameArea(Square square){
		if(square == null)
			return false;
		if(getSquaresOfWholeAreaWithOpenDoor().contains(square))
			return true;
		return false;
	}
	/**
	 * This method returns the set of squaresOfThisArea
	 * @return 	the set of squaresOfThisArea
	 * 			| Set<Square> area=new HashSet<Square>()
	 * 			| for each  square in this.getSquaresOfthisAreaIfAllDoorsWouldBeClosed()
	 * 			| if(!initialisingSet.contains(square))
	 * 			|   then for each border in square.getAllBorders().values()
	 * 			|		if(border.canPassThrough() && ! border.getSeperatedSquares().get(0)==this)
	 * 			|			then area.addAll(border.getSeperatedSquares().get(0).calculateArea(initialisingSet))
	 * 			| result == area
	 */
	public Set<Square> getSquaresOfWholeAreaWithOpenDoor(){
		return calculateArea(new HashSet<Square>());
	}
	/**
	 * This method returns the nbSquaresOfArea of this square(here we keep in mind that an open door expands the area.
	 * @return the size of the area of this square
	 * 		| Set<Square> area = calculateArea(new HashSet<Square>())
	 * 		| result == area.size()
	 */
	public int getNbSquaresOfArea(){
		Set<Square> area = calculateArea(new HashSet<Square>());
		return area.size();
	}
	/**
	 * this method calculates the area to which this square belongs
	 * @param initialisingSet is the set of squares that are already checked previously
	 * @return the set of squares to which this square belongs.
	 * 			| Set<Square> area=new HashSet<Square>()
	 * 			| for each  square in this.getSquaresOfthisAreaIfAllDoorsWouldBeClosed()
	 * 			| if(!initialisingSet.contains(square))
	 * 			|   then for each border in square.getAllBorders().values()
	 * 			|		if(border.canPassThrough() && ! border.getSeperatedSquares().get(0)==this)
	 * 			|			then area.addAll(border.getSeperatedSquares().get(0).calculateArea(initialisingSet))
	 * 			| result == area
	 */
	private Set<Square> calculateArea (Set<Square> initialisingSet){
		Set<Square> area=new HashSet<Square>();
		for(Square square: this.squaresOfthisAreaNotSeparatedWithBorder){
			if(!initialisingSet.contains(square)){
				initialisingSet.add(square);
				area.add(square);
				for(Border border: square.getAllBorders().values()){
					if(border==null){
					}
					else if(border.canPassThrough()){
						Square next=border.getSeperatedSquares().get(0);
						if(next==square)
							next=border.getSeperatedSquares().get(1);
						area.addAll(next.calculateArea(initialisingSet));
					}
				}
			}
		}
		return area;
	}
	/**
	 * this method adds the square to squaresOfThisArea
	 * @param square the square to add to squaresOfThisArea
	 * @post the square is added to the set squaresOfThisArea
	 * 		| new.getSquaresOfThisAreaIfAllDoorsWouldBeClosed().contains(square)
	 * @post the size of squaresOfThisAreaIfAllDoorsWouldBeClosed is incremented by 1
	 * 		| new.getSquaresOfThisAreaIfAllDoorsWouldBeClosed().size == getSquaresOfThisAreaIfAllDoorsWouldBeClosed()+1
	 * @throws IllegalArgumentException when the square is not valid for this area
	 * 			| if(! canHaveAsSquareInArea(square))
	 */
	public void addSquareToSetArea(Square square){
		this.squaresOfthisAreaNotSeparatedWithBorder.add(square);
	}
	/**
	 * This method adds a Square to the area
	 * @param square you want to add to this area
	 * @post the square is added to this set 
	 * 		| addSquareToSetArea(square);
	 * @post this square is also added to the area of the other square when it doesn't already exist there
	 * 		| if(! square.getSquaresOfWholeAreaWithOpenDoor().contains(this))
	 * 		| then square.addSquareToSetArea(this);
	 * @post the areaImbalance is solved
	 * 		| resolveAreaInbalance(square)
	 */
	public void addSquareToArea(Square square){
		addSquareToSetArea(square);
		if(! square.getSquaresOfWholeAreaWithOpenDoor().contains(this))
			square.addSquareToSetArea(this);
		resolveAreaImbalance(square);
	}
	/**
	 * 
	 * @param squares the set of squares you want to add to this area
	 * @post the nb of squares in squaresOfthisAreaNotSeparatedWithBorder is incremented with squares.size()
	 * 		| new.getNbSquaresOfAreaNotSeperatedWithBorder() == getNbSquaresOfAreaNotSeperatedWithBorder()+ squares.size()
	 * @post each square in squares is added to the list of squaresOfthisAreaNotSeparatedWithBorder
	 * 		| for each square in squares
	 * 		| new.getSquaresOfthisAreaNotSeperatedWithBorder().contains(square)
	 */
	public void addSquaresToArea(Set<Square> squares){
		this.squaresOfthisAreaNotSeparatedWithBorder.addAll(squares);
		Square otherSquare=(new ArrayList<Square>(squares)).get(0);
		for(Square square: this.squaresOfthisAreaNotSeparatedWithBorder){
			if(square!=this){
				square.squaresOfthisAreaNotSeparatedWithBorder.clear();
				square.squaresOfthisAreaNotSeparatedWithBorder.addAll(this.squaresOfthisAreaNotSeparatedWithBorder);
			}
		}
		resolveAreaImbalance(otherSquare);
	}
	/**
	 * Getter for the Set of Squares squaresOfthisAreaIfAllDoorsWouldBeClosed
	 * @return the Set of Squares squaresOfthisAreaIfAllDoorsWouldBeClosed
	 */
	@Basic
	public Set<Square> getSquaresOfthisAreaNotSeparatedWithBorder(){
		return new HashSet<Square>(squaresOfthisAreaNotSeparatedWithBorder);
	}
	/**
	 * this method replaces all the squares of this area by the given set
	 * @param squares the new set of squares of the area from this square
	 * @post the set is replaced by the given set
	 * 		| new.getSquaresOfThisAreaIfAllDoorsWouldBeClosed()==squares
	 * @post the areaInbalance is solved for each square in the new list
	 * 		| for each square in new.getSquaresOfThisAreaIfAllDoorsWouldBeClosed()
	 * 		| resolveAreaInbalance(square)
	 */
	public void replaceSquaresOfArea(Set<Square> squares ){
		this.squaresOfthisAreaNotSeparatedWithBorder.clear();
		this.squaresOfthisAreaNotSeparatedWithBorder.addAll(squares);
		for(Square square: this.squaresOfthisAreaNotSeparatedWithBorder)
			resolveAreaImbalance(square);
	}
	/**
	 * This method sets the boolean resolveAreaBalance to the proper value
	 * @post if it was false it is set to true
	 * 		| if(getResolveAreaBalance() == false)
	 * 		| then new.getResolveAreaBalance() ==true;
	 * @post if it was true it is set to false
	 * 		| if(getResolveAreaBalance() == true)
	 * 		| then new.getResolveAreaBalance() ==false;
	 */
	public void setResolveAreaBalance(){
		if(resolveAreaBalance == false)
			resolveAreaBalance = true;
		else{
			resolveAreaBalance = false;
		}
	}
	/**
	 * This method detects the whether there is an areaImbalance and calculates how to solve it, it passes the values on to set the temperature or humidity
	 * @param square the square you want to add to the set getSquaresOfThisArea()
	 * @post for each square the temperature is set to the average of all the temperatures if there is an imbalance
	 * 		| if(square.getTemperature() != this.getTemperature())
	 * 		| then for each squareOfArea in getSquaresOfThisArea()
	 * 		|   squareOfArea.getTemperature() == (square.getTemperature()*square.getNbSquaresOfThisArea()+squareOfArea.getTemperature()*getNbSquaresOfArea())/(getNbSquaresOfArea+square.getNbSquaresOfThisArea())
	 * @post for each square the humidity is set to the average of all the humidities when an imbalance occurs
	 * 		| if(square.getHumidity != this.getHumidity())
	 * 		| then for each squareOfArea in getSquaresOfThisArea()
	 * 		|   squareOfArea.getHumidity() == (square.getHumidity()*square.getNbSquaresOfThisArea()+ squareOfArea.getHumidity()*getNbSquaresOfArea())/(getNbSquaresOfArea+square.getNbSquaresOfThisArea())
	 */
	public void resolveAreaImbalance(Square square){
		setResolveAreaBalance();
		square.setResolveAreaBalance();
		List<Square> arrayListOfSquares= new ArrayList<Square>(squaresOfthisAreaNotSeparatedWithBorder);
		BigDecimal nbSquaresNotInAreaSeparatedWithBorder = new BigDecimal(1);
		// voor als er een deur geopend wordt
		boolean door=false;
		if(!squaresOfthisAreaNotSeparatedWithBorder.contains(square)){
			arrayListOfSquares.clear();
			arrayListOfSquares.addAll(square.getSquaresOfWholeAreaWithOpenDoor());
			nbSquaresNotInAreaSeparatedWithBorder = new BigDecimal(square.getNbSquaresOfAreaNotSeparatedWithBorder());
			door=true;
		}
		Square squareOfArea= (new ArrayList<Square>(this.squaresOfthisAreaNotSeparatedWithBorder)).get(0);
		if(squareOfArea==square&&this.getNbSquaresOfAreaNotSeparatedWithBorder()>1)
			squareOfArea=arrayListOfSquares.get(1);
		if(door)
			resolveMinMaxValues(square,squareOfArea); 
		else
			resolveMinMaxValues(squareOfArea,square);
		int areaTemperature=Scale.changeScale(squareOfArea.getTemperatureScale(), Scale.CELCIUS, squareOfArea.getTemperature());
		int temperatureNewSquare = Scale.changeScale(square.getTemperatureScale(), Scale.CELCIUS, square.getTemperature());
		BigDecimal nbSquares = new BigDecimal(arrayListOfSquares.size());
		BigDecimal nbSquaresMinusOne= nbSquares.subtract(nbSquaresNotInAreaSeparatedWithBorder);
		int newTemperature = ((new BigDecimal(areaTemperature).multiply(nbSquaresMinusOne)).add(new BigDecimal(temperatureNewSquare).multiply(nbSquaresNotInAreaSeparatedWithBorder))).divide(nbSquares,4).intValue();
		
		if(!(areaTemperature == temperatureNewSquare)){
			solveTemperatureImbalance(newTemperature);
			// als de deur geopend wordt want dan zit deze square er niet in, deze moet ook nog aangepast worden.
			if(!squaresOfthisAreaNotSeparatedWithBorder.contains(square))
				square.solveTemperatureImbalance(newTemperature);
		}
		
		BigDecimal areaHumidity = squareOfArea.getHumidity();
		BigDecimal humidityNewSquare = square.getHumidity();
		BigDecimal newHumidity = (areaHumidity.multiply(nbSquaresMinusOne).add(humidityNewSquare.multiply(nbSquaresNotInAreaSeparatedWithBorder))).divide(nbSquares,2);
		
		if(!(areaHumidity.doubleValue()== humidityNewSquare.doubleValue())){
			solveHumidityImbalance(newHumidity);
			// als de deur geopend wordt
			if(!squaresOfthisAreaNotSeparatedWithBorder.contains(square))
				square.solveHumidityImbalance(newHumidity);
		}
		
	}
	/**
	 * This method solves the temperature imbalance 
	 * @param temperature is the temperature to set to all the squares in this area
	 * 		| for each square in getSquaresOfThisArea()
	 * 		| if(!square.getResolveAreaBalance())
	 * 		| 	then square.setResolveAreaBalance()
	 * 		| (new square).getTemperature(Scale.changeScale(Scale.CELCIUS, selectedSquare.getTemperatureScale(), temperature));
	 * 		| square.setResolveAreaBalance()
	 */
	private void solveTemperatureImbalance(int temperature){
		for(Square selectedSquare: this.getSquaresOfWholeAreaWithOpenDoor()){
			if(!selectedSquare.getResolveAreaBalance())
				selectedSquare.setResolveAreaBalance();
			selectedSquare.setTemperature(Scale.changeScale(Scale.CELCIUS, selectedSquare.getTemperatureScale(), temperature));
			selectedSquare.setResolveAreaBalance();
		}
	}
	/**
	 * This method solves the humidity imbalance
	 * @param humidity is the new Humidity to set all the squares to
	 * @post all the squares are set to the same humidity.
	 * 		| for each square in getSquaresOfThisArea()
	 * 		| if(!square.getResolveAreaBalance())
	 * 		| 	then square.setResolveAreaBalance()
	 * 		| (new square).getHumidity()==humidity
	 * 		| square.setResolveAreaBalance()
	 */
	private void solveHumidityImbalance(BigDecimal humidity){
		for(Square selectedSquare: this.squaresOfthisAreaNotSeparatedWithBorder){
			if(!selectedSquare.getResolveAreaBalance())
				selectedSquare.setResolveAreaBalance();
			selectedSquare.setHumidity(humidity);
			selectedSquare.setResolveAreaBalance();
		}
	}
	/**
	 * This method resolves the minimum and max values if there exist some inconsistencies in squaresOfThisArea
	 * @param squareOfArea is a square that already existed in the list squaresOfThisArea
	 * @param newSquare is the square you want to add to the list squaresOfThisArea
	 * @post it takes as minimumTemperature the lowest minimumTemperature of the two
	 * 		| if(minOfArea>minOfNewSquare)
	 * 		| then for each square in getSquaresOfThisArea()
	 * 		| 	(new square).getMinTemperature()==(Scale.changeScale(Scale.CELCIUS, square.getTemperatureScale(), newSquare.getMinTemperature()))
	 * 		| else if(minOfArea<minOfNewSquare)
	 * 		| 	then  (new newSquare).getMinTemperature()==(Scale.changeScale(Scale.CELCIUS, newSquare.getTemperatureScale(), squareOfArea.getMinTemperature()));
	 * @post it takes as maximumTemperature the highest maximumTemperature of the two
	 * 		| maxOfArea<maxOfNewSquare)
	 * 		| then for each square in getSquaresOfThisArea()
	 * 		| 	(new square).getMaxTemperature()==(Scale.changeScale(Scale.CELCIUS, square.getTemperatureScale(), newSquare.getMaxTemperature()))
	 * 		| else if(maxOfArea>maxOfNewSquare)
	 * 		| 	then (new newSquare).getMaxTemperature()==(Scale.changeScale(Scale.CELCIUS, newSquare.getTemperatureScale(), squareOfArea.getMaxTemperature()))
	 */
	private void resolveMinMaxValues(Square squareOfArea, Square newSquare){
		int minOfArea= Scale.changeScale(squareOfArea.getTemperatureScale(), Scale.CELCIUS, squareOfArea.getMinTemperature());
		int maxOfArea=Scale.changeScale(squareOfArea.getTemperatureScale(), Scale.CELCIUS, squareOfArea.getMaxTemperature());
		int minOfNewSquare=Scale.changeScale(newSquare.getTemperatureScale(), Scale.CELCIUS, newSquare.getMinTemperature());
		int maxOfNewSquare=Scale.changeScale(newSquare.getTemperatureScale(), Scale.CELCIUS, newSquare.getMaxTemperature());
		if(minOfArea>minOfNewSquare){
			for(Square selectedSquare: this.getSquaresOfWholeAreaWithOpenDoor())
				selectedSquare.setMinTemperature(Scale.changeScale(Scale.CELCIUS, selectedSquare.getTemperatureScale(), minOfNewSquare));
		}
		else if(minOfArea<minOfNewSquare) {
			newSquare.setMinTemperature(Scale.changeScale(Scale.CELCIUS, newSquare.getTemperatureScale(), minOfArea));
		}
		if(maxOfArea<maxOfNewSquare) {
			for(Square selectedSquare: this.getSquaresOfWholeAreaWithOpenDoor())
				selectedSquare.setMaxTemperature(Scale.changeScale(Scale.CELCIUS, selectedSquare.getTemperatureScale(), maxOfNewSquare));
		}
		else if(maxOfArea>maxOfNewSquare) {
			newSquare.setMaxTemperature(Scale.changeScale(Scale.CELCIUS, newSquare.getTemperatureScale(), maxOfArea));
		}
			
	}
	/**
	 * This method controls whether a square can exist in this area
	 * @param square you want to control
	 * @return false if square is null or is terminated or already exists
	 * 		| if(square==null || square.isTerminated || square.isInSameArea(this))
	 * 		| then result == false
	 */
	public boolean canHaveAsSquareInArea(Square square){
		if(square==null)
			return false;
		if(square.isTerminated)
			return false;
		if(square.getSquaresOfthisAreaNotSeparatedWithBorder().contains(this))
			return false;
		return true;
	}
	/**
	 * This method controls whether the squares in the area are valid.
	 * @return each square in squaresOfThisArea cannot be null or terminated or exist more than once.
	 * 		| for each square in getSquaresOfThisArea
	 * 		| if(square ==null || square.isTerminated() || squaresOfthisAreaNotSeparatedWithBorder.contains(square))
	 * 		| then result == false
	 */
	public boolean hasValidSquaresInArea(){
		for(Square square: squaresOfthisAreaNotSeparatedWithBorder){
			squaresOfthisAreaNotSeparatedWithBorder.remove(square);
			if(square ==null || square.isTerminated() || squaresOfthisAreaNotSeparatedWithBorder.contains(square))
				return false;
			squaresOfthisAreaNotSeparatedWithBorder.add(square);
		}
		return true;
	}
	/**
	 * this method returns the number of squares in this area
	 * @return the number of squares in this area
	 * 			| result ==getSquaresOfthisAreaNotSeparatedWithBorder().size()
	 */
	public int getNbSquaresOfAreaNotSeparatedWithBorder(){
		return this.squaresOfthisAreaNotSeparatedWithBorder.size();
	}
	/**
	 * this method removes a square of the set squaresOfThisArea
	 * @param square
	 * @post square is removed from squaresOfThisArea
	 * 		| !(new.getSquaresOfArea().contains(square))
	 * @post the number of squares in squaresOfThisArea is decremented with 1
	 * 		| new.getNbSquaresOfArea() == this.getNbSquaresOfArea()-1
	 */
	public void removeSquareOfAreaNotSeparatedWithBorder(Square square){
		this.squaresOfthisAreaNotSeparatedWithBorder.remove(square);
	}
	/**
	 * Method to set the state of this square to terminated.
	 * @post for each border of this square, this square is removed from the list
	 * 		| for each border in new.getAllborders().values()
	 * 		|	!border.containsSquare(this)
	 * @post the map of borders is cleared
	 * 		| new.getSquaresOfArea().isEmpty()
	 * @post isTerminated is set to true
	 * 		| new.IsTerminated() == true
	 * @post for each square in set squaresOfthisAreaNotSeparatedWithBorder this square is removed
	 * 		| !(new.getSquaresOfthisAreaNotSeparatedWithBorder().contains(this))
	 */
	public void terminate() {
		this.isTerminated = true;
		for(Iterator <Border> it = this.borders.values().iterator();it.hasNext();){
			Border selectedBorder = it.next();
			if(selectedBorder != null){
				it.remove();
				selectedBorder.removeSquare(this);
				}
		}
		for(Square square:this.squaresOfthisAreaNotSeparatedWithBorder){
			if(square!=this)
				square.removeSquareOfAreaNotSeparatedWithBorder(this);
		}
		this.squaresOfthisAreaNotSeparatedWithBorder.clear();
	}
	
	/**
	 * The method that checks whether or not this square is terminated
	 * @return whether or not this square is terminated.
	 */
	public boolean isTerminated() {
		return this.isTerminated;
	}
	/**
	 * Method to check if one can go from this square to the given square.
	 * @param square the square you want to know of whether you can go there.
	 * @return 	Whether or not you can go from this square to the given square.
	 */
	public abstract boolean canNavigateTo(Square square);
}
